import java.text.ParseException;
import java.util.ArrayList;

import org.joda.time.DateTime;
import org.joda.time.IllegalFieldValueException;
import org.joda.time.format.DateTimeFormat;
import org.joda.time.format.DateTimeFormatter;
import org.joda.time.format.DateTimeFormatterBuilder;
import org.joda.time.format.DateTimeParser;

public class AddParser {

	ArrayList<Task> tasks;
	Task newTask;
	String[] dataComponents;
	DateTime deadline;
	DateTime startDate;
	DateTime endDate;
	String tags;
	String label;
	String taskContent;

	private static final String KEYWORD_BY = "-by";
	private static final String KEYWORD_FROM = "-from";
	private static final String KEYWORD_TO = "-to";
	private static final String KEYWORD_PRIORITY = "-!";
	private static final String KEYWORD_LABEL = "-label";

	private static final String ERROR_INVALID_DATE = "Date entered is invalid.\n";
	private static final String ERROR_END_DATE_BEFORE_START_DATE = "End date cannot be before start date\n";
	private static final String ERROR_NO_LABEL_SET = "No label is entered\n";
	private static final String EMPTY = "";

	private static final String DD_MMMM_YYYY_HH_MM = "dd/MMMM/yyyy-HH:mm";
	private static final String DD_MM_YYYY_HH_MM = "dd/MM/yyyy-HH:mm";
	private static final String DD_MMMM_YY_HH_MM = "dd/MMMM/yy-HH:mm";
	private static final String DD_MM_YY_HH_MM = "dd/MM/yy-HH:mm";
	private static final String DD_MMMM_YYYY = "dd/MMMM/yyyy";
	private static final String DD_MM_YYYY = "dd/MM/yyyy";
	private static final String DD_MMMM_YY = "dd/MMMM/yy";
	private static final String DD_MM_YY = "dd/MM/yy";
	
	public Task parse(String data, ArrayList<Task> tasks) throws ParseException {

		newTask = null;
		String taskContent = EMPTY;
		this.tasks = tasks;

		// To allow for more input patterns
		DateTimeParser[] parsers = {
		DateTimeFormat.forPattern(DD_MM_YY).getParser(),
				DateTimeFormat.forPattern(DD_MMMM_YY).getParser(),
				DateTimeFormat.forPattern(DD_MM_YYYY).getParser(),
				DateTimeFormat.forPattern(DD_MMMM_YYYY).getParser(),
				DateTimeFormat.forPattern(DD_MM_YY_HH_MM).getParser(),
				DateTimeFormat.forPattern(DD_MMMM_YY_HH_MM).getParser(),
				DateTimeFormat.forPattern(DD_MM_YYYY_HH_MM).getParser(),
				DateTimeFormat.forPattern(DD_MMMM_YYYY_HH_MM).getParser(), };

		DateTimeFormatter formatter = new DateTimeFormatterBuilder().append(
				null, parsers).toFormatter();

		data = data.trim();

		if (isFloatingTask(data)) {
			taskContent = data;
			newTask = new Task(taskContent);
			createFloatingTask(data);

		} else {
			if (data.contains(KEYWORD_BY)) {
				createDeadlineTask(data, taskContent, formatter);
			} else {
				createTimedTask(data, taskContent, formatter);
			}
			if (data.contains(KEYWORD_LABEL)) {
				setLabelForTask(data);
			}
			if (data.contains(KEYWORD_PRIORITY)) {
				newTask.setPriority(true);
			}
		}

		newTask.setUserInput(data);

		return newTask;
	}

	private void setLabelForTask(String data) throws ParseException {
		dataComponents = data.split(KEYWORD_LABEL);
		try {
			tags = obtainTags(dataComponents);
			if (tags.contains(KEYWORD_PRIORITY)) {
				tags = tags.replace(KEYWORD_PRIORITY, EMPTY);
				label = tags.trim();
				newTask.setLabel(label);
				newTask.setPriority(true);
			} else {
				label = tags.trim();
				newTask.setLabel(label);
			}
			if (newTask.getLabel().equals(EMPTY)) {
				throw new ParseException(ERROR_NO_LABEL_SET, 1);
			}
		} catch (Exception e) {
			throw new ParseException(ERROR_NO_LABEL_SET, 2);
		}
	}

	private void createTimedTask(String data, String taskContent,
			DateTimeFormatter formatter) throws ParseException {
		try {
			dataComponents = data.split(KEYWORD_FROM);
			taskContent = obtainTaskContent(dataComponents);
			startDate = obtainDateTime(data, KEYWORD_FROM, formatter);
			dataComponents = data.split(KEYWORD_TO);
			endDate = obtainDateTime(data, KEYWORD_TO, formatter);
		} catch (Exception e) {
			throw new ParseException(ERROR_INVALID_DATE, 0);
		}
		if (endDate.isBefore(startDate)) {
			throw new ParseException(ERROR_END_DATE_BEFORE_START_DATE, 1);
		}

		newTask = new Task(taskContent, startDate, endDate);
	}

	private void createDeadlineTask(String data, String taskContent,
			DateTimeFormatter formatter) throws ParseException {
		dataComponents = data.split(KEYWORD_BY);
		try {
			taskContent = obtainTaskContent(dataComponents);
			deadline = obtainDateTime(data, KEYWORD_BY, formatter);
		} catch (Exception e) {
			throw new ParseException(ERROR_INVALID_DATE, 0);
		}
		newTask = new Task(taskContent, deadline);
	}

	private DateTime obtainDateTime(String data, String keyword,
			DateTimeFormatter formatter) throws IllegalFieldValueException {

		DateTime datetime;
		try {
			String[] dataComponents = data.split(keyword);
			String dateString = dataComponents[1].trim().split(" ")[0];
			datetime = formatter.parseDateTime(dateString);
		} catch (Exception e) {
			throw new IllegalFieldValueException(ERROR_INVALID_DATE,
					ERROR_INVALID_DATE);
		} 
		return datetime;

	}

	private boolean isFloatingTask(String data) {
		return !data.contains(KEYWORD_BY) && !data.contains(KEYWORD_FROM)
				&& !data.contains(KEYWORD_TO);
	}

	private String obtainTaskContent(String[] dataComponents) {
		return dataComponents[0].trim();
	}

	private String obtainTags(String[] dataComponents) {
		return dataComponents[1];
	}

	private void createFloatingTask(String data) throws ParseException {
		String taskContent;
		if (data.contains(KEYWORD_LABEL)) {
			dataComponents = data.split(KEYWORD_LABEL);
			try {
				taskContent = obtainTaskContent(dataComponents);
				newTask.setContent(taskContent);
				tags = obtainTags(dataComponents);
				if (tags.contains(KEYWORD_PRIORITY)) {
					floatTaskSetLabelAndPriority();
				} else {
					label = tags.trim();
					newTask.setLabel(label);
				}
			} catch (ArrayIndexOutOfBoundsException e) {
				throw new ParseException(ERROR_NO_LABEL_SET, 1);
			}
			if(data.contains(KEYWORD_PRIORITY)){
				taskContent = taskContent.replace(KEYWORD_PRIORITY, EMPTY);
				newTask.setContent(taskContent);
				newTask.setPriority(true);
			}

		} else if (data.contains(KEYWORD_PRIORITY)) {
			floatTaskSetPriority(data);
		}
	}

	private void floatTaskSetPriority(String data) {
		taskContent = data.replace(KEYWORD_PRIORITY, EMPTY);
		taskContent = taskContent.trim();
		newTask.setContent(taskContent);
		newTask.setPriority(true);
	}

	private void floatTaskSetLabelAndPriority() throws ParseException {
		dataComponents = tags.split(KEYWORD_PRIORITY);
		label = dataComponents[0].trim();
		newTask.setLabel(label);
		if (newTask.getLabel().equals(EMPTY)) {
			throw new ParseException(ERROR_NO_LABEL_SET, 1);
		}
		newTask.setPriority(true);
	}

}
